package uk.org.brindy.osgi.fvis.bundle;

import java.util.Dictionary;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.packageadmin.PackageAdmin;

@SuppressWarnings("unchecked")
public class BundleToJSON {

	private static final String QUOTE = "\"";
	private static final String COLON = ":";
	private static final String NULL = "null";
	private static final String COMMA = ",";
	private static final String OPEN_OBJECT = "{";
	private static final String CLOSE_OBJECT = "}";
	private static final String OPEN_ARRAY = "[";
	private static final String CLOSE_ARRAY = "]";

	private final BundleContext context;

	private boolean dirty = true;

	private String json;

	public BundleToJSON(BundleContext context) {
		this.context = context;
		context.addBundleListener(new BundleListener() {
			public void bundleChanged(BundleEvent event) {
				dirty = true;
			}
		});
	}

	public boolean isDirty() {
		return dirty;
	}

	private synchronized String createJSON() {

		StringBuffer buffer = new StringBuffer();

		buffer.append(OPEN_ARRAY).append("\n");
		int count = 0;

		Bundle[] bundles = context.getBundles();

		// build a wiring map
		ServiceReference ref = context.getServiceReference(PackageAdmin.class
				.getName());
		PackageAdmin admin = (PackageAdmin) context.getService(ref);
		try {
			for (Bundle bundle : bundles) {
				if (count > 0) {
					buffer.append(COMMA);
					buffer.append("\n");
				}
				buffer.append(OPEN_OBJECT);
				appendBundle(bundle, admin, buffer);
				buffer.append(CLOSE_OBJECT);
				count++;
			}

		} finally {
			context.ungetService(ref);
		}

		buffer.append("\n");
		buffer.append(CLOSE_ARRAY);
		return buffer.toString();
	}

	private void appendBundle(Bundle bundle, PackageAdmin admin,
			StringBuffer buffer) {
		Dictionary headers = bundle.getHeaders();

		addNamedHeader(Constants.BUNDLE_SYMBOLICNAME, headers, buffer);

		buffer.append(COMMA);
		addNamedHeader(Constants.BUNDLE_VERSION, headers, buffer);

		// use PackageAdmin to work out wiring.
		ExportedPackage[] pkgs = admin.getExportedPackages(bundle);
		addExportedPackages(buffer, pkgs);

		appendRegisteredServices(bundle.getRegisteredServices(), buffer);
		appendServicesInUse(bundle.getServicesInUse(), buffer);
	}

	private void addExportedPackages(StringBuffer buffer, ExportedPackage[] pkgs) {
		if (pkgs != null && pkgs.length > 0) {
			buffer.append(COMMA);

			addName("Exported-Packages", buffer);

			buffer.append(OPEN_ARRAY);

			for (int i = 0; i < pkgs.length; i++) {
				ExportedPackage pkg = pkgs[i];
				if (i > 0) {
					buffer.append(COMMA);
				}

				buffer.append(OPEN_OBJECT);

				// add basic package info, name and version
				addNamedValue("name", pkg.getName(), buffer);

				Version version = pkg.getVersion();
				if (null != version) {
					buffer.append(COMMA);
					addNamedValue("version", version.toString(), buffer);
				}

				// add which bundles are using it
				Bundle[] importingBundles = pkg.getImportingBundles();
				addImportingBundles(buffer, importingBundles);

				buffer.append(CLOSE_OBJECT);
			}

			buffer.append(CLOSE_ARRAY);
		}
	}

	private void addImportingBundles(StringBuffer buffer,
			Bundle[] importingBundles) {
		if (importingBundles != null && importingBundles.length > 0) {

			buffer.append(COMMA);

			addName("Imported-By", buffer);

			buffer.append(OPEN_ARRAY);

			for (int j = 0; j < importingBundles.length; j++) {
				Bundle importBundle = importingBundles[j];

				if (j > 0) {
					buffer.append(COMMA);
				}

				buffer.append(OPEN_OBJECT);

				addNamedValue(Constants.BUNDLE_SYMBOLICNAME, importBundle
						.getSymbolicName(), buffer);

				buffer.append(COMMA);

				addNamedValue(Constants.BUNDLE_VERSION, (String) importBundle
						.getHeaders().get(Constants.BUNDLE_VERSION), buffer);

				buffer.append(CLOSE_OBJECT);
			}

			buffer.append(CLOSE_ARRAY);

		}
	}

	private void appendServicesInUse(ServiceReference[] refs,
			StringBuffer buffer) {

		if (null == refs || refs.length == 0) {
			return;
		}
		buffer.append(COMMA);

		addName("Services-In-Use", buffer);

		buffer.append(OPEN_ARRAY);

		for (int i = 0; i < refs.length; i++) {
			ServiceReference ref = refs[i];

			if (i > 0) {
				buffer.append(COMMA);
			}

			buffer.append(OPEN_OBJECT);

			addNamedValue(Constants.BUNDLE_SYMBOLICNAME, ref.getBundle()
					.getSymbolicName(), buffer);
			buffer.append(COMMA);

			addNamedValue(Constants.BUNDLE_VERSION, (String) ref.getBundle()
					.getHeaders().get(Constants.BUNDLE_VERSION), buffer);
			buffer.append(COMMA);

			addObjectClasses(buffer, ref);

			buffer.append(CLOSE_OBJECT);
		}

		buffer.append(CLOSE_ARRAY);
	}

	private void appendRegisteredServices(ServiceReference[] refs,
			StringBuffer buffer) {

		if (null == refs || refs.length == 0) {
			return;
		}
		buffer.append(COMMA);

		addName("Registered-Services", buffer);

		buffer.append(OPEN_ARRAY);

		for (int i = 0; i < refs.length; i++) {
			ServiceReference ref = refs[i];

			if (i > 0) {
				buffer.append(COMMA);
			}

			buffer.append(OPEN_OBJECT);
			addObjectClasses(buffer, ref);
			buffer.append(CLOSE_OBJECT);
		}

		buffer.append(CLOSE_ARRAY);
	}

	private void addObjectClasses(StringBuffer buffer, ServiceReference ref) {
		Object o = ref.getProperty(Constants.OBJECTCLASS);

		addName(Constants.OBJECTCLASS, buffer);

		buffer.append(OPEN_ARRAY);

		if (o instanceof String) {
			addValue((String) o, buffer);
		} else if (o instanceof String[]) {
			String[] values = (String[]) o;
			for (int i = 0; i < values.length; i++) {
				if (i > 0) {
					buffer.append(COMMA);
				}
				addValue(values[i], buffer);
			}
		}

		buffer.append(CLOSE_ARRAY);
	}

	private void addNamedHeader(String name, Dictionary headers,
			StringBuffer buffer) {
		String value = (String) headers.get(name);
		String[] values = value.split(";");
		addNamedValue(name, values[0].trim(), buffer);
	}

	private void addNamedValue(String name, String value, StringBuffer buffer) {
		addName(name, buffer);
		addValue(value, buffer);
	}

	private void addValue(String value, StringBuffer buffer) {
		if (null == value) {
			buffer.append(NULL);
		} else if (quoted(value)) {
			buffer.append(value);
		} else {
			buffer.append(QUOTE).append(value).append(QUOTE);
		}
	}

	private boolean quoted(String value) {
		return value.startsWith(QUOTE);
	}

	private void addName(String name, StringBuffer buffer) {
		buffer.append(QUOTE).append(name).append(QUOTE).append(COLON);
	}

	public String toString() {
		if (dirty) {
			synchronized (this) {
				json = createJSON();
			}
		}

		return json;
	}

}
